// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/bybit_futures/order_v3/parser.h"

#include <cmath>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::bybit_futures::order_v3 {

namespace {

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::ProductPosition;
using coin::proto::SubmitOrderResponse;
using coin2::exchange::base::order::IOrderContext;
using coin2::exchange::bybit_futures::symbology::BybitFuturesCurrency;
using coin2::exchange::bybit_futures::symbology::BybitFuturesProduct;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("orderLinkId")) {
    std::string link_order_str(data["orderLinkId"].GetString());
    if (!link_order_str.empty()) {
      proc_order_id = stoll(link_order_str);
    }
  }
  return proc_order_id;
}

inline coin::proto::OrderSide GetOrderSide(const std::string& side) {
  if (side == "Buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "Sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline coin::proto::OrderType GetOrderType(const std::string& type) {
  if (type == "Limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "Market") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void SetOrderStatus(const std::string& status, ProductOrderElement* order) {
  if (status == "Created" || status == "Rejected" || status == "PendingCancel") {
    order->set_is_live(false);
  } else if (status == "New" || status == "PartiallyFilled") {
    order->set_is_live(true);
  } else if (status == "Cancelled") {
    order->set_cancel_confirmed(true);
  } else if (status == "Filled") {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

inline coin::proto::OrderErrorCode GetOrderErrorCodeFromBybitFutures(
    const JsonValue& json_error_msg) {
  using coin::proto::OrderErrorCode;
  auto result = coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR;
  std::string_view error_code(json_error_msg.GetString());

  if (error_code == "0") {
    result = OrderErrorCode::ORDER_NO_ERROR;
  } else if (error_code == "10006") {
    result = OrderErrorCode::API_LIMIT_ERROR;
  } else if (error_code == "20071") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30010") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30021") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30031") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30049") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30067") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "140007") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "20001") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == "30032") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == "30034") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == "30037") {
    result = OrderErrorCode::ORDER_CANCELED_ALREADY;
  }

  return result;
}

bool IsBybitFuturesErrMessage(const JsonValue& doc) {
  return doc.HasMember("retCode") && std::string_view(doc["retCode"].GetString()) != "0";
}

}  // namespace

void BybitFuturesParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BybitFuturesParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Bybit");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    // only parse USDT for linear
    if (is_linear() && native_currency != "USDT") {
      continue;
    }
    // ignore USDT for non-linear
    if (!is_linear() && native_currency == "USDT") {
      continue;
    }
    double total = raw_balance_map.count(native_currency) == 0
                       ? 0.
                       : raw_balance_map.at(native_currency).total();
    if (!CompareLastBalance(native_currency, total, doc)) {
      continue;
    }
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BybitFuturesParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "list": [
        {
          "coin": "BTC",
          "equity": "0",
          "walletBalance": "0",
          "positionMargin": "0",
          "availableBalance": "0",
          "orderMargin": "0",
          "occClosingFee": "0",
          "occFundingFee": "0",
          "unrealisedPnl": "0",
          "cumRealisedPnl": "0",
          "givenCash": "0",
          "serviceCash": "0"
        },
        ...
      ]
    },
    "retExtInfo": {},
    "time": 1669097427875
  }
  */
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRawBalance] " << JsonToString(doc);
    return false;
  }
  if (!doc.HasMember("result") || !doc["result"].IsObject()) {
    LOG(ERROR) << "[ParseRawBalance] invalid json. " << JsonToString(doc);
    return false;
  }
  if (!doc["result"].HasMember("list") || !doc["result"]["list"].IsArray()) {
    LOG(ERROR) << "[ParseRawBalance] invalid json. " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["result"]["list"].GetArray()) {
    if (!data.HasMember("equity") || !data.HasMember("availableBalance")) {
      continue;
    }
    double total = stod(data["equity"].GetString());
    double avail = stod(data["availableBalance"].GetString());

    CurrencyBalance balance;
    balance.set_currency_native(data["coin"].GetString());
    balance.set_total(total);
    balance.set_hold(total - avail);
    balance.set_available(avail);
    raw_balance_map->emplace(balance.currency_native(), balance);
  }

  return true;
}

AccountBalance BybitFuturesParser::ParseBalanceFromWs(const JsonValue& doc) {
  /*
  {
    "topic": "user.wallet.contractAccount",
    "data": [
      {
        "coin": "USDT",
        "equity": "199.99303808",
        "walletBalance": "199.99403808",
        "positionMargin": "1.10306921",
        "availableBalance": "197.29369363",
        "orderMargin": "1.59727524",
        "unrealisedPnl": "-0.001",
        "cumRealisedPnl": "-0.00596192"
      }
    ]
  }
  */
  AccountBalance acc_balance;
  if (!(doc.HasMember("data") && doc["data"].IsArray())) {
    LOG(ERROR) << "[ParseBalanceFromWs] " << JsonToString(doc);
    return acc_balance;
  }

  acc_balance.set_exchange("Bybit");
  acc_balance.set_market_type("Futures");
  for (const auto& data : doc["data"].GetArray()) {
    // balance:
    //   total - wallet_balance
    //   avail - available_balance
    //   hold  - order_margin
    if (!symcache_->has_currency_native(data["symbol"].GetString())) {
      continue;
    }
    if (!data.HasMember("wallet_balance") || !data.HasMember("available_balance")) {
      continue;
    }
    double total = stod(data["wallet_balance"].GetString());
    double avail = stod(data["available_balance"].GetString());
    double hold = total - avail;

    auto* currency = symcache_->GetCurrencyByNative(data["symbol"].GetString());
    auto* currency_balance = acc_balance.add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    currency_balance->set_available(avail);
    currency_balance->set_hold(hold);
    currency_balance->set_total(total);
  }

  return acc_balance;
}

void BybitFuturesParser::ParseProductPosition(
    const JsonValue& data,
    AccountPosition* acc_position) {
  if (!data.HasMember("symbol") || !data.HasMember("side") || !data.HasMember("size")) {
    LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
    return;
  }

  if (std::string_view("0") != data["positionIdx"].GetString()) {
    LOG(ERROR) << "@OG team, please check. only One-Way mode is supported for v3.\n"
               << JsonToString(data);
    return;
  }

  auto* position = acc_position->add_each_position();

  position->set_native_symbol(data["symbol"].GetString());
  std::string_view side(data["side"].GetString());
  double pos_net = stod(data["size"].GetString());
  if (side == "Buy") {
    position->set_long_position(pos_net);
    position->set_net_position(pos_net);
  } else if (side == "Sell") {
    // TODO cann't set short position and net position = -pos_net at same time
    position->set_short_position(-pos_net);
    position->set_net_position(-pos_net);
  } else if (side == "None" && pos_net == 0) {
    position->set_short_position(0);
    position->set_long_position(0);
    position->set_net_position(0);
  } else {
    LOG(ERROR) << "[ParsePosition] Unexpected side! " << JsonToString(data);
  }
}

bool BybitFuturesParser::ParseProductPositionV2(
    const JsonValue& data,
    RawPositionMap* raw_position_map) {
  if (!data.HasMember("symbol") || !data.HasMember("side") || !data.HasMember("size")) {
    LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
    return false;
  }

  if (std::string_view("0") != data["positionIdx"].GetString()) {
    LOG(ERROR) << "@OG team, please check. only One-Way mode is supported for v3.\n"
               << JsonToString(data);
    return false;
  }

  std::string_view side(data["side"].GetString());
  double pos_net = stod(data["size"].GetString());
  if (side != "Buy" && side != "Sell" && !(side == "None" && pos_net == 0)) {
    LOG(ERROR) << "[ParsePosition] Unexpected side! " << JsonToString(data);
    return false;
  }

  auto native_symbol = data["symbol"].GetString();
  auto& position = (*raw_position_map)[native_symbol];

  if (side == "Buy") {
    position.set_long_position(pos_net);
    position.set_net_position(pos_net);
  } else if (side == "Sell") {
    position.set_short_position(-pos_net);
    position.set_net_position(-pos_net);
  } else if (side == "None" && pos_net == 0) {
    position.set_short_position(0);
    position.set_long_position(0);
    position.set_net_position(0);
  } else {
    NOTREACHED() << "Unexpected position data! " << JsonToString(data);
  }

  return true;
}

void BybitFuturesParser::ParseRestAccountPosition() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  ignorable_position_event_ = false;
  auto account_position = ParsePosition(document);
  if (ignorable_position_event_) {
    result_.set_type(MsgType::IGNORABLE);
    return;
  }
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition BybitFuturesParser::ParsePosition(const JsonValue& doc) {
  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return {};
  }

  // update position cache
  setlle_coin_cache_set_.emplace(extra_info_);
  for (const auto& [native_symbol, position] : raw_position_map) {
    position_cache_map_[native_symbol] = position;
  }
  if (setlle_coin_cache_set_.size() != symcache_->GetCurrenciesNativeUpper().size()) {
    // not ready for all symbols
    ignorable_position_event_ = true;
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Bybit");
  acc_position.set_market_type("Futures");
  if (is_account_position_initialized_) {
    // update position for single rest query
    for (auto& [native_symbol, pos] : raw_position_map) {
      auto* position = acc_position.add_each_position();
      *position = std::move(pos);
      position->set_native_symbol(native_symbol);
      DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
    }
  } else {
    // update position for all symbols
    for (const auto& native_symbol : symcache_->GetProductNative()) {
      if (position_cache_map_.count(native_symbol) != 0) {
        continue;
      }
      auto* position = acc_position.add_each_position();
      position->set_long_position(0);
      position->set_short_position(0);
      position->set_net_position(0);
      position->set_available_long_position(0);
      position->set_available_short_position(0);
      position->set_native_symbol(native_symbol);

      DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
    }

    for (auto& pair : position_cache_map_) {
      auto* position = acc_position.add_each_position();
      *position = std::move(pair.second);
      position->set_native_symbol(pair.first);
      DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
    }
    is_account_position_initialized_ = true;
  }

  return acc_position;
}

bool BybitFuturesParser::ParseRawPosition(const JsonValue& doc, RawPositionMap* raw_position_map) {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "list": [
        {
          "positionIdx": 0,
          "riskId": "11",
          "symbol": "ETHUSDT",
          "side": "None",
          "size": "0.00",
          "positionValue": "0",
          "entryPrice": "0",
          "tradeMode": 0,
          "autoAddMargin": 0,
          "leverage": "10",
          "positionBalance": "0",
          "liqPrice": "0.00",
          "bustPrice": "0.00",
          "takeProfit": "0.00",
          "stopLoss": "0.00",
          "trailingStop": "0.00",
          "unrealisedPnl": "0",
          "createdTime": "1668652189456",
          "updatedTime": "1669097417590",
          "tpSlMode": "Full",
          "riskLimitValue": "900000",
          "activePrice": "0.00"
        }
      ]
    },
    "retExtInfo": {},
    "time": 1669097430344
  }
  */
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }
  if (!doc.HasMember("result") || !doc["result"].HasMember("list") ||
      !doc["result"]["list"].IsArray()) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["result"]["list"].GetArray()) {
    if (!ParseProductPositionV2(data, raw_position_map)) {
      return false;
    }
  }
  return true;
}

ProductOrderElement BybitFuturesParser::ParseRestOrderInfo(const JsonValue& data) {
  ProductOrderElement order;
  order.set_market_type(coin::proto::MarketType::Futures);
  order.set_exchange_type(coin::proto::ExchangeType::Bybit);
  order.set_api_version("v3");
  order.set_exchange_order_id(data["orderId"].GetString());
  order.set_symbol(data["symbol"].GetString());
  order.set_price(stod(data["price"].GetString()));
  order.set_qty(stod(data["qty"].GetString()));
  order.set_proc_order_id(GetProcOrderId(data));
  order.set_side(GetOrderSide(data["side"].GetString()));
  order.set_order_type(GetOrderType(data["orderType"].GetString()));
  order.set_timestamp(msg_->timestamp);
  SetOrderStatus(data["orderStatus"].GetString(), &order);
  return order;
}

void BybitFuturesParser::ParseRestOrdersInfo() {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "list": [
        {
          "symbol": "BTCUSDT",
          "side": "Sell",
          "orderType": "Limit",
          "price": "15850.50",
          "qty": "0.001",
          "reduceOnly": false,
          "timeInForce": "PostOnly",
          "orderStatus": "Cancelled",
          "leavesQty": "0.000",
          "leavesValue": "0",
          "cumExecQty": "0.000",
          "cumExecValue": "0",
          "cumExecFee": "0",
          "lastPriceOnCreated": "0.00",
          "rejectReason": "EC_PerCancelRequest",
          "orderLinkId": "1669095449368698038",
          "createdTime": "1669095476008",
          "updatedTime": "1669095478443",
          "orderId": "72d74bd4-3907-4ec2-8854-6c2feb305b14",
          "stopOrderType": "UNKNOWN",
          "takeProfit": "0.00",
          "stopLoss": "0.00",
          "tpTriggerBy": "UNKNOWN",
          "slTriggerBy": "UNKNOWN",
          "triggerPrice": "0.00",
          "closeOnTrigger": false,
          "triggerDirection": 0,
          "positionIdx": 0
        },
        ...
      ],
      "nextPageCursor": "..."
    },
    "retExtInfo": {},
    "time": 1669097432889
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] " << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("result") || !doc["result"].HasMember("list") ||
      !doc["result"]["list"].IsArray()) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Unexpected order list format! " << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  for (const auto& info : doc["result"]["list"].GetArray()) {
    auto* order = product_order_bundle->add_each_order();
    auto new_order = ParseRestOrderInfo(info);
    order->MergeFrom(new_order);
  }
  result_.set_type(MsgType::ORDER_STATUS);
}

ProductOrderBundle BybitFuturesParser::ParseWsOrderInfoListV2(const JsonValue& order_data) {
  /*
  {
    "topic": "user.order.contractAccount",
    "data": [
      {
        "symbol": "ETHUSDT",
        "orderId": "a0d78216-20df-4b1c-9900-2f200bedb464",
        "side": "Sell",
        "orderType": "Limit",
        "stopOrderType": "UNKNOWN",
        "price": "1096.60",
        "qty": "0.01",
        "timeInForce": "PostOnly",
        "orderStatus": "New",
        "triggerPrice": "0.00",
        "orderLinkId": "1669097398284434210",
        "createdTime": "1669097577365",
        "updatedTime": "1669097577369",
        "takeProfit": "0.00",
        "stopLoss": "0.00",
        "tpTriggerBy": "UNKNOWN",
        "slTriggerBy": "UNKNOWN",
        "triggerBy": "UNKNOWN",
        "reduceOnly": false,
        "closeOnTrigger": false,
        "triggerDirection": 0,
        "leavesQty": "0.01",
        "lastExecQty": "0",
        "lastExecPrice": "0.00",
        "cumExecQty": "0",
        "cumExecValue": "0"
      }
    ]
  }
  */
  ProductOrderBundle product_order_bundle;
  if (!order_data.IsObject() || IsBybitFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseWsOrderInfoList] " << JsonToString(order_data);
    return product_order_bundle;
  }

  if (!(order_data.HasMember("data") && order_data["data"].IsArray())) {
    LOG(ERROR) << "Unexpected ws order list format! " << JsonToString(order_data);
    return product_order_bundle;
  }

  for (const auto& data : order_data["data"].GetArray()) {
    auto* order = product_order_bundle.add_each_order();
    auto new_order = ParseRestOrderInfo(data);
    order->MergeFrom(new_order);
  }
  return product_order_bundle;
}

void BybitFuturesParser::ParseRestFillInfoList() {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "list": [
        {
          "symbol": "BTCUSDT",
          "execFee": "-0.0015853",
          "execId": "fd993e53-3804-56d2-8670-66ca18b27d0d",
          "execPrice": "15853.00",
          "execQty": "0.001",
          "execType": "Trade",
          "execValue": "15.853",
          "feeRate": "-0.0001",
          "lastLiquidityInd": "AddedLiquidity",
          "leavesQty": "0.000",
          "orderId": "8c29f7ae-04ad-4cba-9646-e56431f3fe2d",
          "orderLinkId": "1669095449368698039",
          "orderPrice": "15853.00",
          "orderQty": "0.001",
          "orderType": "Limit",
          "stopOrderType": "UNKNOWN",
          "side": "Sell",
          "execTime": "1669095478626",
          "closedSize": "0.001"
        },
        ...
      ],
      "nextPageCursor": "..."
    },
    "retExtInfo": {},
    "time": 1669097432899
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestFillInfoList] " << JsonToString(doc);
    return;
  }
  if (!(doc.HasMember("result") && doc["result"].HasMember("list") &&
        doc["result"]["list"].IsArray())) {
    LOG(ERROR) << "[ParseRestFillInfoList] Unexpected fill list format! " << JsonToString(doc);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  for (const auto& data : doc["result"]["list"].GetArray()) {
    auto* fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(data["symbol"].GetString());
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(data["orderId"].GetString());
    fill->set_proc_order_id(GetProcOrderId(data));
    fill->set_price(stod(data["execPrice"].GetString()));
    fill->set_qty(stod(data["execQty"].GetString()));
    fill->set_fill_id(data["execId"].GetString());
    fill->set_fee(stod(data["execFee"].GetString()));
    std::string_view fill_type(data["lastLiquidityInd"].GetString());
    if (fill_type == "AddedLiquidity") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "RemovedLiquidity") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
  }
  result_.set_type(MsgType::ORDER_FILL);
}

ProductFillBundle BybitFuturesParser::ParseWsFillInfoListV2(const JsonValue& order_data) {
  /*
  {
    "topic": "user.execution.contractAccount",
    "data": [
      {
        "symbol": "ETHUSDT",
        "execFee": "-0.00109615",
        "execId": "13f4c374-6933-5847-923b-d4b892653793",
        "execPrice": "1096.15",
        "execQty": "0.01",
        "execType": "Trade",
        "execValue": "10.9615",
        "feeRate": "-0.0001",
        "lastLiquidityInd": "AddedLiquidity",
        "leavesQty": "0",
        "orderId": "9d576530-9c39-4d89-9a56-9e7b925074e8",
        "orderLinkId": "1669097398284434196",
        "orderPrice": "1096.15",
        "orderQty": "0.01",
        "orderType": "Limit",
        "stopOrderType": "UNKNOWN",
        "side": "Buy",
        "execTime": "1669097569706",
        "closedSize": "0"
      }
    ]
  }
  */
  ProductFillBundle product_fill_bundle;
  if (!order_data.IsObject() || IsBybitFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseWsFillInfoList] " << JsonToString(order_data);
    return product_fill_bundle;
  }

  if (!(order_data.HasMember("data") && order_data["data"].IsArray())) {
    LOG(ERROR) << "Unexpected ws fill list format! " << JsonToString(order_data);
    return product_fill_bundle;
  }

  for (const auto& data : order_data["data"].GetArray()) {
    auto* fill = product_fill_bundle.add_each_fill();
    fill->set_symbol(data["symbol"].GetString());
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(data["orderId"].GetString());
    fill->set_proc_order_id(GetProcOrderId(data));
    fill->set_price(stod(data["execPrice"].GetString()));  // TODO(linchuan) : no exec_price
    fill->set_qty(stod(data["execQty"].GetString()));
    fill->set_fill_id(data["execId"].GetString());
    fill->set_fee(stod(data["execFee"].GetString()));
    std::string_view fill_type(data["lastLiquidityInd"].GetString());
    if (fill_type == "AddedLiquidity") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "RemovedLiquidity") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
  }
  return product_fill_bundle;
}

void BybitFuturesParser::ParseSubmitResponse() {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "orderId": "fc275342-5917-4fd5-aa4b-046543c784cd",
      "orderLinkId": "1669097398284434140"
    },
    "retExtInfo": {},
    "time": 1669097414234
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (IsBybitFuturesErrMessage(json)) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] Error Response" << http_context->res;
    const std::string ret_code = json["retCode"].GetString();
    if (ret_code == "10016") {
      // exchange internal service process timeout, uncertain order status
      result_.Clear();
      result_.set_type(MsgType::IGNORABLE);
    } else {
      resp->set_error_code(GetOrderErrorCodeFromBybitFutures(json["retCode"]));
      resp->set_exchange_error_code(ret_code);
      if (ret_code == "140007") {
        resp->set_exchange_error_msg("Insufficient available balance");
      } else {
        resp->set_exchange_error_msg(json["retMsg"].GetString());
      }
      resp->set_success(false);
    }
    return;
  }

  if (!(json.HasMember("result") && json["result"].HasMember("orderId"))) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] exchange order id not found\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  resp->set_exchange_order_id(json["result"]["orderId"].GetString());
}

void BybitFuturesParser::ParseCancelResponse() {
  /*
  {
    "retCode": 0,
    "retMsg": "OK",
    "result": {
      "orderId": "849cc4e5-c442-4bdf-aff9-941abf993198",
      "orderLinkId": "1669097398284434141"
    },
    "retExtInfo": {},
    "time": 1669097434784
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (IsBybitFuturesErrMessage(json)) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] Error Response" << http_context->res;
    const std::string ret_code = json["retCode"].GetString();
    if (ret_code == "10016") {
      // exchange internal service process timeout, uncertain order status
      result_.Clear();
      result_.set_type(MsgType::IGNORABLE);
    } else {
      resp->set_error_code(GetOrderErrorCodeFromBybitFutures(json["retCode"]));
      resp->set_exchange_error_code(ret_code);
      resp->set_exchange_error_msg(json["retMsg"].GetString());
      resp->set_success(false);
    }
    return;
  }

  if (json.HasMember("result") && json["result"].HasMember("orderId")) {
    resp->set_exchange_order_id(std::string(json["result"]["orderId"].GetString()));
  }

  // cancel accepted only
  resp->set_success(true);
}

// 2020-6-4: receive balance doubled, hold == available, filter
// 2021-1-10: receive zero balance when bybit system upgrading, filter
// 20220825(daniel): in case it's due to transfer, ignore the abnormal balance update only in 2 mins
bool BybitFuturesParser::CompareLastBalance(
    const std::string& symbol,
    double total_balance,
    const JsonValue& doc) {
  if (last_total_balance_.find(symbol) == last_total_balance_.end()) {
    last_total_balance_.insert(std::make_pair(symbol, total_balance));
    return true;
  }
  if (total_balance > last_total_balance_[symbol] * 1.5 ||
      total_balance < last_total_balance_[symbol] * 0.5) {
    auto now = GetCurrentTimestamp();
    if (abnormal_balance_.count(symbol) == 0) {
      abnormal_balance_[symbol] = now;
      LOG(ERROR)
          << " the balance of " << symbol
          << " suddenly change a lot! prev: " << last_total_balance_[symbol]
          << ", curr: " << total_balance
          << ", this change is ignored and it will be updated after 2 mins if it consistents\n"
          << JsonToString(doc);
    }
    if (now < abnormal_balance_.at(symbol) + 120'000'000'000LL) {
      return false;
    }
  }
  if (abnormal_balance_.count(symbol) > 0) {
    abnormal_balance_.erase(symbol);
  }
  last_total_balance_[symbol] = total_balance;

  return true;
}

AccountPosition BybitFuturesParser::ParsePositionFromWs(const JsonValue& doc) {
  /*
  {
    "topic": "user.position.contractAccount",
    "data": [
      {
        "positionIdx": 0,
        "riskId": 11,
        "riskLimitValue": "900000",
        "symbol": "ETHUSDT",
        "side": "Buy",
        "size": "0.01",
        "positionValue": "10.9615",
        "entryPrice": "1096.15",
        "tradeMode": 0,
        "autoAddMargin": 0,
        "leverage": "10",
        "positionBalance": "1.10306921",
        "liqPrice": "0.05",
        "bustPrice": "0.05",
        "takeProfit": "0.00",
        "stopLoss": "0.00",
        "trailingStop": "0.00",
        "unrealisedPnl": "-0.001",
        "createdTime": "1668652189456",
        "updatedTime": "1669097577369",
        "tpSlMode": "Full"
      }
    ]
  }
   */
  AccountPosition acc_position;
  if (!is_account_position_initialized_) {
    return acc_position;
  }
  if (!(doc.HasMember("data") && doc["data"].IsArray())) {
    LOG(ERROR) << "[ParseNormalPositionFromWs] unexpected format. \n" << JsonToString(doc);
    return acc_position;
  }

  acc_position.set_exchange("Bybit");
  acc_position.set_market_type("Futures");
  for (const auto& data : doc["data"].GetArray()) {
    ParseProductPosition(data, &acc_position);
  }
  return acc_position;
}

bool BybitFuturesParser::ParseHeader(
    const std::shared_ptr<AsioHttpContext>& http_context,
    std::string* rate_limit_info) {
  bool ret = false;
  nlohmann::json json = {
      {"X-Bip-Get-Limit-Status", -1},
      {"X-Bip-Post-Limit-Status", -1},
      {"X-Bapi-Limit-Status", -1},
      {"X-Bapi-Limit", -1},
      {"X-Bapi-Limit-Reset-Timestamp", -1},
      {"X-Bapi-Limit-Endpoint", ""},
      {"bind_ip", http_context->bind_ip}};

  const auto& status = http_context->res.result_int();
  if (status == 403) {
    json["X-Bip-Get-Limit-Status"] = 0;
    json["X-Bip-Post-Limit-Status"] = 0;
    ret = true;
  } else {
    const auto& header = http_context->res.base();
    if (header.count("X-Bapi-Limit-Status") > 0) {
      json["X-Bapi-Limit-Status"] = std::stoi(header.at("X-Bapi-Limit-Status").data());
      ret = true;
    }
    if (header.count("X-Bapi-Limit") > 0) {
      json["X-Bapi-Limit"] = std::stoi(header.at("X-Bapi-Limit").data());
      ret = true;
    }
    if (header.count("X-Bapi-Limit-Reset-Timestamp") > 0) {
      json["X-Bapi-Limit-Reset-Timestamp"] =
          std::stoll(header.at("X-Bapi-Limit-Reset-Timestamp").data());
      ret = true;
    }
    std::string target = http_context->target;
    const auto pos = target.find("?");
    json["X-Bapi-Limit-Endpoint"] = pos == std::string::npos ? target : target.substr(0, pos);
  }
  if (ret) {
    *rate_limit_info = json.dump();
  }

  return ret;
}

void BybitFuturesParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!document.HasMember("topic")) {
    result_.set_type(MsgType::IGNORABLE);
    return;
  }

  if (std::string_view(document["topic"].GetString()) == "user.wallet.contractAccount") {
    // daniel(20210830): ignored ws balance update due to it's complicated to
    // identifty that if a symbol is inverse nor not
    // if (is_account_balance_initialized_) {
    //   auto account_balance = ParseBalanceFromWs(document);
    //   if (account_balance.has_exchange() && account_balance.has_market_type()) {
    //     result_.set_type(MsgType::ACCOUNT_BALANCE);
    //     *result_.mutable_account_balance() = std::move(account_balance);
    //   }
    // }
  } else if (std::string_view(document["topic"].GetString()) == "user.position.contractAccount") {
    AccountPosition account_position = ParsePositionFromWs(document);
    if (account_position.has_exchange() && account_position.has_market_type()) {
      result_.set_type(MsgType::ACCOUNT_POSITION);
      *result_.mutable_account_position() = std::move(account_position);
    }
  } else if (std::string_view(document["topic"].GetString()) == "user.order.contractAccount") {
    *result_.mutable_order_list() = ParseWsOrderInfoListV2(document);
    result_.set_type(MsgType::ORDER_STATUS);
  } else if (std::string_view(document["topic"].GetString()) == "user.execution.contractAccount") {
    *result_.mutable_fill_list() = ParseWsFillInfoListV2(document);
    result_.set_type(MsgType::ORDER_FILL);
  }
}

auto BybitFuturesParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg.type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  std::string rate_limit_info;
  if (ParseHeader(msg.http_context, &rate_limit_info)) {
    mb_->PublishEvent("bybit_futures/req_rate_limit", reinterpret_cast<void*>(&rate_limit_info));
  }

  extra_info_ = msg.http_context->extra_info;
  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseRestOrdersInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillInfoList();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_POSITION:
      ParseRestAccountPosition();
      break;
    default: {
      LOG(ERROR) << "[BybitFutures] Parsing Error.\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bybit_futures::order_v3
